using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Threading;
using System.Data;
using Rotempco.Core.ORM.Objects;
using System.Configuration;
using Rotempco.Core.ORM.Objects.Stores;

namespace Rotempco.Core.ORM.Managers
{
    public class DatabaseManager
    {
        # region [ Singleton Constructor ]

        public DatabaseManager()
        {
            _SqlConnections = new Dictionary<string, Dictionary<int, SqlConnection>>();
            _SqlTransactions = new Dictionary<string, Dictionary<int, SqlTransaction>>();
            _DBSSqlConnections = new Dictionary<int, string>();
            _TransactionIdByThreadId = new Dictionary<int, Guid>();
        }

        public static DatabaseManager Instance
        {
            get
            {
                return SingletonStore.Instance.GetInstance<DatabaseManager>(false);
            }
        }

        # endregion

        //private string _CurrentDatabase { get; set; }
        private Dictionary<string, Dictionary<int, SqlConnection>> _SqlConnections;
        private Dictionary<string, Dictionary<int, SqlTransaction>> _SqlTransactions;
        private Dictionary<int, string> _DBSSqlConnections;

        private Dictionary<int, Guid> _TransactionIdByThreadId;

        internal string GetCurrentDatabase()
        {
            if (_DBSSqlConnections.ContainsKey(ThreadId))
                return _DBSSqlConnections[ThreadId];

            return DatabaseSettingsStore.Instance.CurrentDatabase.ConnectionString;
        }
                
        private void CloseConnection(DBConnection dbSelector)
        {
            string database = dbSelector.ConnectionString;

            if (_SqlConnections.ContainsKey(database) &&
                _SqlConnections[database].ContainsKey(ThreadId) &&
                _SqlConnections[database][ThreadId] != null)
            {
                _SqlConnections[database][ThreadId].Close();
            }

            if (_SqlConnections.ContainsKey(database))
                _SqlConnections[database].Remove(ThreadId);

            if (_DBSSqlConnections.ContainsKey(ThreadId))
                _DBSSqlConnections.Remove(ThreadId);
        }


        #region [ Database Specific Transaction ]

        private void Transact(DBConnection dbSelector, Action action, int countDown)
        {
            bool inTransaction = false;
            try
            {
                inTransaction = BeginTransaction(dbSelector);

                action();

                if (inTransaction == false)
                    CommitTransaction(dbSelector);
            }
            catch (Exception exception)
            {
                if (inTransaction == false)
                {
                    RollbackTransaction(dbSelector, "Exception accurred", exception);

                    if (IsPresumedDeadlock(exception) && countDown > 0)
                    {
                        Thread.Sleep(new Random().Next(10, 250));
                        Transact(action, countDown--);
                    }
                    else
                        throw;
                }
                else
                    throw;
            }
        }

        #endregion

        # region [ Audit Transactions ]

        public void AuditTransact(Action action)
        {
            if (_TransactionIdByThreadId.ContainsKey(ThreadId))
                _TransactionIdByThreadId.Remove(ThreadId);

            _TransactionIdByThreadId.Add(ThreadId, Guid.NewGuid());

            Transact(action);

            if (_TransactionIdByThreadId.ContainsKey(ThreadId))
                _TransactionIdByThreadId.Remove(ThreadId);
        }

        public Guid? GetCurrentAuditTransactionId()
        {
            if (!_TransactionIdByThreadId.ContainsKey(ThreadId))
                return null;

            return _TransactionIdByThreadId[ThreadId];
        }

        # endregion

        #region [ Transactions ]

        public void Transact(Action action)
        {
            Transact(action, 10);
        }

        private void Transact(Action action, int countDown)
        {
            Transact(new DBConnection(), action, countDown);
        }

        private bool IsPresumedDeadlock(Exception exception)
        {
            return exception is SqlException && exception.Message.ToLower().Contains("deadlock victim");
        }

        private bool ExistingTransaction(DBConnection dbSelector)
        {
            string database = dbSelector.ConnectionString;

            bool ExistingTransaction = false;
            if (_SqlTransactions.ContainsKey(database))
            {
                if (_SqlTransactions[database].ContainsKey(ThreadId))
                {
                    ExistingTransaction = true;
                }
            }
            else
            {
                _SqlTransactions.Add(database, new Dictionary<int, SqlTransaction>());
            }
            return ExistingTransaction;
        }

        private bool ExistingConnection(DBConnection dbSelector)
        {
            string database = dbSelector.ConnectionString;

            bool ExistingConnection = false;
            if (_SqlConnections.ContainsKey(database))
            {
                if (_SqlConnections[database].ContainsKey(ThreadId))
                {
                    ExistingConnection = true;
                }
            }
            else
            {
                _SqlConnections.Add(database, new Dictionary<int, SqlConnection>());
            }
            return ExistingConnection;
        }

        public bool IsThreadInTransaction()
        {
            string database = GetCurrentDatabase();
            return ExistingTransaction(new DBConnection(database));
        }

        private bool BeginTransaction(DBConnection dbSelector)
        {
            string database = dbSelector.ConnectionString;

            bool InTransaction;
            lock (_SqlConnections)
            {
                if (ExistingConnection(dbSelector) == false)
                {
                    _SqlConnections[database].Add(ThreadId, GetSqlConnection(database));
                    _SqlConnections[database][ThreadId].Open();
                }

                if (ExistingTransaction(dbSelector) == false)
                {
                    _SqlTransactions[database][ThreadId] = _SqlConnections[database][ThreadId].BeginTransaction();

                    if (_DBSSqlConnections.ContainsKey(ThreadId))
                        _DBSSqlConnections.Remove(ThreadId);

                    _DBSSqlConnections.Add(ThreadId, database);

                    InTransaction = false;
                }
                else
                    InTransaction = true;
            }
            return InTransaction;
        }

        private void CommitTransaction(DBConnection dbSelector)
        {
            string database = dbSelector.ConnectionString;

            if (_SqlTransactions.ContainsKey(database) &&
                _SqlTransactions[database].ContainsKey(ThreadId) &&
                _SqlTransactions[database][ThreadId] != null)
            {
                _SqlTransactions[database][ThreadId].Commit();
            }

            EndTransaction(dbSelector);
        }

        private void RollbackTransaction(DBConnection dbSelector, string reason, Exception exception)
        {
            string database = dbSelector.ConnectionString;

            if (_SqlTransactions.ContainsKey(database) &&
                _SqlTransactions[database].ContainsKey(ThreadId) &&
                _SqlTransactions[database][ThreadId] != null)
            {
                _SqlTransactions[database][ThreadId].Rollback();
            }

            try
            {
                //Logit.EmailEnabled = false;
                //Logit.Error(reason, exception);
            }
            finally
            {
                //Logit.EmailEnabled = true;
            }

            EndTransaction(dbSelector);
        }

        private void EndTransaction(DBConnection dbSelector)
        {
            string database = dbSelector.ConnectionString;

            CloseConnection(dbSelector);

            if (_SqlTransactions.ContainsKey(database))
                _SqlTransactions[database].Remove(ThreadId);
        }

        #endregion

        #region [ Internal Database Methods ]

        private int ThreadId
        {
            get
            {
                return Thread.CurrentThread.ManagedThreadId;
            }
        }

        internal void CloseConnection(SqlConnection sqlConnection)
        {
            CloseConnection(sqlConnection, GetCurrentDatabase());
        }

        internal void CloseConnection(SqlConnection sqlConnection, string database)
        {
            if (!_SqlConnections.ContainsKey(database) ||
                !_SqlConnections[database].ContainsKey(ThreadId) ||
                _SqlConnections[database][ThreadId] == null)
                sqlConnection.Close();
        }

        internal SqlConnection GetSqlConnection(string database)
        {
            if (_SqlConnections.ContainsKey(database) &&
                _SqlConnections[database].ContainsKey(ThreadId) &&
                _SqlConnections[database][ThreadId] != null)
            {
                return _SqlConnections[database][ThreadId];
            }


            string connectionString = database;// DatabaseSettingsStore.Instance.ConnectionString;
            SqlConnection sqlConnection = new SqlConnection(connectionString);

            return sqlConnection;
        }

        internal SqlConnection SqlConnection
        {
            get
            {
                return GetSqlConnection(GetCurrentDatabase());
            }
        }

        internal SqlCommand GetCommand(string database)
        {
            return GetCommand(SqlConnection, database);
        }

        internal SqlCommand GetCommand(SqlConnection sqlConnection)
        {
            return GetCommand(sqlConnection, GetCurrentDatabase());
        }

        internal SqlCommand GetCommand(SqlConnection sqlConnection, string database)
        {
            SqlCommand sqlCommand = sqlConnection.CreateCommand();
            if (_SqlTransactions.ContainsKey(database) &&
                _SqlTransactions[database].ContainsKey(ThreadId) &&
                _SqlTransactions[database][ThreadId] != null)
            {
                sqlCommand.Transaction = _SqlTransactions[database][ThreadId];
            }

            return sqlCommand;
        }

        internal void OpenConnection(SqlConnection sqlConnection)
        {
            if (sqlConnection.State != ConnectionState.Open)
                sqlConnection.Open();
        }

        #endregion

        #region [ Set Temporary Database ]

        public void RunOnDatabase(DBConnection dbSelector, Action action)
        {
            try
            {
                SetTemporaryDatabaseConnection(dbSelector);
                action();
            }
            finally
            {
                _DBSSqlConnections.Remove(ThreadId);
                CloseConnection(dbSelector);
            }
        }

        private void SetTemporaryDatabaseConnection(DBConnection dbSelector)
        {
            string database = dbSelector.ConnectionString;

            lock (_SqlConnections)
            {
                if (ExistingConnection(dbSelector) == false)
                {
                    _SqlConnections[database].Add(ThreadId, GetSqlConnection(database));
                    _SqlConnections[database][ThreadId].Open();
                }

                if (_DBSSqlConnections.ContainsKey(ThreadId))
                    _DBSSqlConnections.Remove(ThreadId);

                _DBSSqlConnections.Add(ThreadId, database);
            }
        }

        #endregion


    }
}
